﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Xml;
using Pampa.CRUD.Exceptions;

namespace Pampa.CRUD.Core
{
    public class EntityRepositoryAR : IEntityRepository
    {
        #region Edit Functions

        /// <summary>
        /// Return true if the type implements IActiveRecord
        /// </summary>
        public bool IsEntity(Type type)
        {
            Type entityInterface = type.GetInterface("IActiveRecord");
            return (entityInterface != null);
        }

        /// <summary>
        /// Check if an entity is new
        /// </summary>
        public bool IsNew(object entity)
        {
            CheckIfActiveRecord(entity);

            var ientity = (IActiveRecord)entity;
            return ientity.IsNew;
        }

        public ITransaction BeginTransaction(object entity)
        {
            CheckIfActiveRecord(entity);

            var ientity = (IActiveRecord)entity;
            ientity.BeginTransaction();

            return new TransactionAR(ientity);
        }

        public void Save(object entity)
        {
            CheckIfActiveRecord(entity);

            var ientity = (IActiveRecord)entity;
            ientity.Save();
        }

        public void Delete(object entity)
        {
            CheckIfActiveRecord(entity);

            var ientity = (IActiveRecord)entity;
            ientity.Delete();
        }

        public object Find(Type entityType, object id)
        {
            return (IActiveRecord)ExecuteMethod(entityType, "Find", new Type[1] { typeof(object) }, new object[1] { id });
        }

        public string GetId(object entity)
        {
            CheckIfIdentifiable(entity);

            return ((IIdentifiable) entity).Id;
        }

        public object ParseId(Type entityType, string strId)
        {
            return ExecuteMethod(entityType, "ParseId", new Type[1] { typeof(string) }, new object[1] { strId });
        }

        #endregion

        #region List Functions

        /// <summary>
        /// Obtiene una lista (id, valor) con todas las entidades del tipo entityName que haya en la base
        /// </summary>
        public IList GetList(string entityQualifiedName)
        {
            Type[] paramTypes = new Type[0];
            object[] paramValues = new object[0];
            return (IList)ExecuteMethod(entityQualifiedName, "FindAll", paramTypes, paramValues);
        }

        /// <summary>
        /// Obtiene una lista (id, valor) con todas las entidades del tipo entityName que haya en la base
        /// ordenadas por el cambo 'orderBy'
        /// </summary>
        public IList GetList(string entityQualifiedName, string orderBy)
        {
            if (String.IsNullOrEmpty(orderBy))
            {
                return GetList(entityQualifiedName);
            }
            else
            {
                Type[] paramTypes = new Type[1] { typeof(string) };
                object[] paramValues = new object[1] { orderBy };
                return (IList)ExecuteMethod(entityQualifiedName, "FindAll", paramTypes, paramValues);
            }
        }

        /// <summary>
        /// Obtiene una lista (id, valor) con todas las entidades del tipo entityName que haya en la base
        /// que cumplan con "entitity.property = value"
        /// </summary>
        public IList GetList(string entityQualifiedName, string property, string strPropertyValue)
        {
            object propertyValue = EntityManager.GetPropertyValueFromString(entityQualifiedName, property, strPropertyValue);

            Type[] paramTypes = new Type[2] { typeof(string), typeof(object) };
            object[] paramValues = new object[2] { property, propertyValue };
            return (IList)ExecuteMethod(entityQualifiedName, "FindAllByProperty", paramTypes, paramValues);
        }

        /// <summary>
        /// Obtiene una lista (id, valor) con todas las entidades del tipo entityName que haya en la base 
        /// que cumplan con "entitity.property = value" ordenadas por el cambo 'orderBy'
        /// Solo se puede pedir el GetList de entidades que implementen la interfaz IIdDescription
        /// </summary>
        public IList GetList(string entityQualifiedName, string property, string strPropertyValue, string orderBy)
        {
            if (String.IsNullOrEmpty(orderBy))
            {
                return GetList(entityQualifiedName, property, strPropertyValue);
            }
            else
            {
                var entityType = Type.GetType(entityQualifiedName);
                object propertyValue;

                if (typeof(IGenericEntity).IsAssignableFrom(entityType))
                {
                    var genericEntity = (IGenericEntity)Activator.CreateInstance(entityType);
                    genericEntity.SetPropertyValueFromString(property, strPropertyValue);
                    propertyValue = genericEntity.GetPropertyValue(property);
                }
                else
                {
                    propertyValue = EntityManager.GetPropertyValueFromString(entityQualifiedName, property, strPropertyValue);
                }

                Type[] paramTypes = new Type[3] { typeof(string), typeof(object), typeof(string) };
                object[] paramValues = new object[3] { property, propertyValue, orderBy };
                return (IList)ExecuteMethod(entityQualifiedName, "FindAllByProperty", paramTypes, paramValues);
            }
        }

        /// <summary>
        /// Obtiene una lista (id, valor) con todas las entidades del tipo entityName que haya en la base 
        /// que cumplan con "entity.[likeProperty] like '%value%'"
        /// </summary>
        public IList GetList(string entityQualifiedName, string likeProperty, string likeValue, int maxresults)
        {
            Type[] paramTypes = new Type[] { typeof(string), typeof(string), typeof(int) };
            object[] parameters = new object[] { likeProperty, likeValue, maxresults };
            return (IList)ExecuteMethod(entityQualifiedName, "FindAllByPropertyLike", paramTypes, parameters);
        }

        /// <summary>
        /// Obtiene una lista (id, valor) con todas las entidades del tipo entityName que haya en la base 
        /// que cumplan con "entity.[likeProperty] like '%value%'" y "entitity.[filterProperty] = filterPropertyValue"
        /// </summary>
        public IList GetList(string entityQualifiedName, string likeProperty, string likeValue, int maxresults, string filterProperty, string strFilterPropertyValue)
        {
            object filterPropertyValue = EntityManager.GetPropertyValueFromString(entityQualifiedName, filterProperty, strFilterPropertyValue);

            Type[] paramTypes = new Type[] { typeof(string), typeof(string), typeof(int), typeof(string), typeof(object) };
            object[] parameters = new object[] { likeProperty, likeValue, maxresults, filterProperty, filterPropertyValue };
            return (IList)ExecuteMethod(entityQualifiedName, "FindAllByPropertyLike", paramTypes, parameters);
        }

        #endregion

        #region Private Functions

        private void CheckIfActiveRecord(object entity)
        {
            if (!(entity is IActiveRecord))
            {
                throw new EntityManagerException(string.Format("Entity {0} should implement IActiveRecord. Alternatively you can use your own EntityRepository.", entity.GetType().Name));
            }
        }

        private void CheckIfIdentifiable(object entity)
        {
            if (!(entity is IIdentifiable))
            {
                throw new EntityManagerException(string.Format("Entity {0} should implement IIdentifiable. Alternatively you can use your own EntityRepository.", entity.GetType().Name));
            }
        }

        #endregion

        #region Reflexion

        /// <summary>
        /// Este metodo permite ejecutar un método estatico de una entidad determinada,
        /// encapsulando el acceso por reflection
        /// </summary>
        /// <param name="entityQualifiedName">Nombre del tipo de la entidad</param>
        /// <param name="methodName">Nombre del método a ejecutar</param>
        /// <param name="paramTypes">Parametros</param>
        /// <param name="paramValues">Parametros</param>
        /// <returns></returns>
        public static object ExecuteMethod(string entityQualifiedName, string methodName, Type[] paramTypes, object[] paramValues)
        {
            Type entityType = Type.GetType(entityQualifiedName);

            if (entityType == null)
            {
                throw (new EntityManagerException("There is no entity with name '" + entityQualifiedName + "'"));
            }
            else
            {
                return ExecuteMethod(entityType, methodName, paramTypes, paramValues);
            }
        }

        public static object ExecuteMethod(Type entityType, string methodName, Type[] paramTypes, object[] paramValues)
        {
            MethodInfo method = entityType.GetMethod(methodName, BindingFlags.Static | BindingFlags.Public | BindingFlags.FlattenHierarchy, null, paramTypes, null);

            if (method != null)
            {
                return method.Invoke(null, paramValues);
            }
            else
            {
                throw (new EntityManagerException("La clase " + entityType.Name + " debe implementar el método estático " + methodName));
            }
        }

        #endregion
    }
}
